#include "sys.h"
#include "net.h"
#include "log.h"
#include "uentry.h"
#include "us_ctl.h"

#define DEFAULT_RETRY_COUNT 10

static int cli_ctl_snd(int fd, uctl_conf_t* uconf, uctl_conf_t* resp_uconf, int retry)
{
    int ret;
    char rcv_buffer[UCTL_BUFFER_MAX] = { 0 }, snd_buf[UCTL_BUFFER_MAX] = { 0 };

    uctl_conf_struct2str(uconf, snd_buf, sizeof(snd_buf));
    while (1) {
        log_trace("cli send %s", snd_buf);
        ret = write(fd, snd_buf, strlen(snd_buf));
        if (ret < 0) {
            log_syserr("send error.");
            return 0;
        }

        if (resp_uconf) {
            bzero(rcv_buffer, sizeof(rcv_buffer));
            ret = read(fd, rcv_buffer, sizeof(rcv_buffer));
            if (ret > 0) {
                log_trace("cli rcv resp:%s", rcv_buffer);
                uctl_conf_str2struct(resp_uconf, rcv_buffer, ret);
                return 1;
            }

        } else {
            break;
        }
        if (!retry--)
            return 0;
    }
    return 1;
}

static int cli_request(int ctl_fd, uint16_t* udp_port, int retry)
{
    uctl_conf_t uconf = { 0 }, resp_uconf = { 0 };

    strcpy(uconf.cmd, UCTL_CONF_CMD_REQ);
    if (cli_ctl_snd(ctl_fd, &uconf, &resp_uconf, DEFAULT_RETRY_COUNT)) {
        *udp_port = resp_uconf.port;
        return 1;
    }
    return 0;
}

static int cli_download(int fd)
{
    int ret, retry_count = DEFAULT_RETRY_COUNT;
    uint64_t total_bytes = 0, total_packets = 0;
    char buffer[UCTL_BUFFER_MAX] = { 0 };
    uint64_t last_seq;
    uctl_conf_t uconf = { 0 };
    uentry_trans_info_t download_info = { 0 };

    strcpy(uconf.cmd, UCTL_CONF_CMD_DOWNLOAD);
    uconf.rate = 50 * 1000 * 1000;
    uconf.duration = 10000;
    uconf.blksize = 1450;

retry:
    cli_ctl_snd(fd, &uconf, NULL, 0);

    while (1) {
        ret = read(fd, buffer, sizeof(buffer));
        if (ret == 0 || ret < 0)
            break;
        download_info.bytes += ret;
        download_info.packets++;
        last_seq = *((uint64_t*)buffer);
    }
    log_trace("total_bytes %lld, total_packets:%lld", download_info.bytes, download_info.packets);
    log_trace("last_seq:%llu", last_seq);
    log_trace("lost percent:%.2f%%", Positive((1 - ((double)download_info.packets / last_seq)) * 100));
    if (download_info.bytes == 0 && retry_count > 0) {
        retry_count--;
        goto retry;
    }
    return 1;
}
static int cli_upload(int udp_fd)
{
    int ret;
    char buffer[UCTL_BUFFER_MAX] = { 0 };
    struct timeval start, now, end;
    double ms_start, ms_now, ms_end;
    uint64_t total_bytes = 0, total_packets = 0;
    uctl_conf_t uconf = { 0 }, resp_uconf = { 0 };
    uentry_trans_info_t send_info = { 0 };

    strcpy(uconf.cmd, UCTL_CONF_CMD_UPLOAD);
    uconf.rate = 50 * 1000 * 1000;
    uconf.duration = 10000;
    uconf.blksize = 1450;
    
    if (!cli_ctl_snd(udp_fd, &uconf, &resp_uconf, DEFAULT_RETRY_COUNT))
        return 0;

    gettimeofday(&start, NULL);
    ms_start = TV2MS(&start);
    ms_end = ms_start + uconf.duration;
    if (!strcmp(resp_uconf.cmd, UCTL_CONF_CMD_ACK)) {
        while (1) {
            if (!ratelimit(TV2US(&start), send_info.bytes, uconf.rate))
                continue;
            ret = write(udp_fd, buffer, uconf.blksize);
            if (ret < 0) {
                log_syserr("upload write error.");
                return 0;
            }
            send_info.bytes += ret;
            send_info.packets++;

            gettimeofday(&now, NULL);
            ms_now = TV2MS(&now);
            if (ms_now > ms_end) {
                break;
            }
        }
    }

    bzero(&uconf, sizeof(uconf));
    strcpy(uconf.cmd, UCTL_CONF_CMD_UPLOAD_FINISH);
    if (!cli_ctl_snd(udp_fd, &uconf, &resp_uconf, DEFAULT_RETRY_COUNT))
        return 0;
    if (!strcmp(resp_uconf.cmd, UCTL_CONF_CMD_UPLOAD_REPORT)) {
        log_trace("rcv bytes:%llu", resp_uconf.bytes);
        log_trace("rcv packets:%llu", resp_uconf.packets);
        log_trace("send bytes:%llu", send_info.bytes);
        log_trace("send packets:%llu", send_info.packets);
        log_trace("lost percent:%.2f%%", Positive((1 - ((double)resp_uconf.packets / send_info.packets)) * 100));

        return 1;
    }
    return 0;
}
static void cli_close(int fd)
{
    uctl_conf_t uconf = { 0 }, resp_uconf = { 0 };

    strcpy(uconf.cmd, UCTL_CONF_CMD_CLOSE);
    cli_ctl_snd(fd, &uconf, &resp_uconf, 5);
    if (!strcmp(resp_uconf.cmd, UCTL_CONF_CMD_CLOSE)) {
        return;
    }
}
int us_cli_run(uint16_t ctl_port, char* peer_host)
{
    int ret;
    int ctl_fd = 0, udp_fd = 0, max_fd = 0;
    uint16_t udp_port;
    fd_set rdfds;
    struct timeval tmo;
    char buffer[UCTL_BUFFER_MAX] = { 0 };
    struct sockaddr_in peer_addr;
    uctl_conf_t uconf;

    ctl_fd = socket_dgram_new();
    sockaddr_set(&peer_addr, AF_INET, htons(ctl_port), peer_host);

    sock_rcvtmo(ctl_fd, 2, 0);
    connect(ctl_fd, (struct sockaddr*)&peer_addr, sizeof(peer_addr));

    if (!cli_request(ctl_fd, &udp_port, 5)) {
        return 0;
    }
    log_trace("Get udp port %d", udp_port);

    udp_fd = socket_dgram_new();
    sock_rcvtmo(udp_fd, 2, 0);
    sockaddr_set(&peer_addr, AF_INET, htons(udp_port), peer_host);
    connect(udp_fd, (struct sockaddr*)&peer_addr, sizeof(peer_addr));

    if (!cli_download(udp_fd)) {
        log_trace("cli download failed.");
        return 0;
    }
    if (!cli_upload(udp_fd)) {
        log_trace("cli upload failed.");
        return 0;
    }
    cli_close(udp_fd);
    return 1;
}